<template>
  <div ref="orgTreeContainer" class="org-tree-wrap">
    <div :id="treeId"></div>
  </div>
</template>

<script>
  import * as d3 from 'd3'
  export default {
    name: 'CommonOrgTree',
    props: {
      treeId: {
        type: String,
        default: 'treeSvg',
      },
      data: {
        type: Object,
        default: () => {
          return {}
        },
      },
      url: {
        type: String,
        default: '',
      },
      svgConfig: {
        type: Object,
        default: () => {
          return {
            y: 40, //y轴下移40px
          }
        },
      },
      arrowConfig: {
        type: Object,
        default: () => {
          return { color: '#409eff' }
        },
      },
      circleConfig: {
        type: Object,
        default: () => {
          return {
            r: 7,
            padding: 3,
            fillColor: '#409eff',
            strokeColor: '#fff',
          }
        },
      },
      linkConfig: {
        type: Object,
        default: () => {
          return {
            lineColor: '#409eff',
            textColor: '#409eff',
            height: 140,
            textFields: [],
          }
        },
      },
      nodeConfig: {
        type: Object,
        default: () => {
          return {
            fields: ['name', 'id', 'GDP'],
            height: 75,
            width: 100,
          }
        },
      },
    },
    setup(props) {
      const DEFAULTSVGCONFIG = {
        y: 40,
      }
      const DEFAULTARROWCONFIG = {
        color: '#409eff',
      }
      const DEFAULTCIRCLECONFIG = {
        r: 7,
        padding: 3,
        fillColor: '#409eff',
        strokeColor: '#fff',
      }
      const DEFAULTLINKCONFIG = {
        lineColor: '#409eff',
        textColor: '#409eff',
        height: 140,
      }
      const DEFAULTNODECONFIG = {
        fields: ['name', 'id', 'GDP'],
        height: 75,
        width: 100,
      }
      let treeData = null
      const orgTreeContainer = ref(null)
      let treeMap = null
      let svg = null
      let timeContaniner = null

      let maxLayerNodes = []

      /**
       * 获取树结构深度
       */
      const getTreedeep = (treeData, sum = 0) => {
        let sums = []
        const num = maxLayerNodes[sum] ?? 0
        const length = treeData.children ? treeData.children?.length : 0
        maxLayerNodes[sum] = num + length
        if (treeData.children) {
          sums = treeData.children.map((item) => {
            const sumC = sum
            if (item.children) {
              return getTreedeep(item, sumC + 1)
            } else {
              return sumC + 1
            }
          })
        }

        let value = 1

        if (sums.length) {
          value = sums.reduce((pre, cur) => {
            if (cur > pre) return cur
            return pre
          })
        }
        return value
      }

      /**
       *创建箭头
       * @param {*} svg  初始化创建的svg
       */
      const createMark = () => {
        const { arrowConfig = DEFAULTARROWCONFIG } = props
        const marker = svg
          .append('marker')
          .attr('id', 'resolved')
          .attr('markerUnits', 'strokeWidth') //设置为strokeWidth箭头会随着线的粗细发生变化
          .attr('markerUnits', 'userSpaceOnUse')
          .attr('viewBox', '0 -5 10 10') //坐标系的区域
          .attr('refX', 10) //箭头坐标
          .attr('refY', 0)
          .attr('markerWidth', 12) //标识的大小
          .attr('markerHeight', 12)
          .attr('orient', 'auto') //绘制方向，可设定为：auto（自动确认方向）和 角度值
          .attr('stroke-width', 2) //箭头宽度
          .append('path')
          .attr('d', 'M0,-5L10,0L0,5') //箭头的路径
          .attr('fill', arrowConfig.color) //箭头颜色

        return marker
      }

      /**
       * 节点收缩子节点符号
       * @param {*} node 节点
       */
      const drawCircle = (node) => {
        const { r, padding, fillColor, strokeColor } = {
          ...props.circleConfig,
          ...DEFAULTCIRCLECONFIG,
        }
        const { width, height } = { ...DEFAULTNODECONFIG, ...props.nodeConfig }
        console.warn(111111111, width, height)
        const gMark = node
          .append('g')
          .style('display', (d) => {
            if (!d.data._children) {
              return 'none'
            }
          })
          .attr('class', 'node-circle')
          .attr('transform', `translate(${width / 2},${height + r})`)

        gMark
          .append('circle')
          .attr('fill', 'none')
          .attr('r', (d) => (d.depth === 0 ? 0 : r)) //根节点不设置圆圈
          .attr('fill', fillColor)
        gMark
          .append('path')
          .attr('d', `m -${padding} 0 l ${2 * padding} 0`)
          .attr('stroke', strokeColor) //横线

        gMark
          .append('path') //竖线，根据展开/收缩动态控制显示
          .attr('d', `m 0 -${padding} l 0 ${2 * padding}`)
          .attr('stroke-width', 1)
          .attr('stroke', strokeColor)
          .attr('class', 'node-circle-vertical')
        return gMark
      }

      /**
       * 点击节点展开收缩子节点
       * @param {*} d
       */
      const clickNode = (d) => {
        if (!d.data._children && !d.data.children) {
          //无子节点
          return
        }
        if (d.data.children) {
          d.data._children = d.data.children
          d.data.children = null
        } else {
          d.data.children = d.data._children
          d.data._children = null
        }
        //**清除画布 */
        console.warn(111111, props.treeId)
        d3.select(`#${props.treeId}`).selectAll('svg').remove()
        updateTree()
      }

      /**
       * 初始化树
       */
      const initSvgTree = () => {
        const { y } = { ...DEFAULTSVGCONFIG, ...props.svgConfig }
        const { height, width } = { ...DEFAULTNODECONFIG, ...props.nodeConfig }
        maxLayerNodes = []
        const treeDepth = getTreedeep(treeData)
        //算出每层node占位最长的层级
        const maxNodesWidth = maxLayerNodes.reduce((pre, cur, index) => {
          const maxWidth = cur * (width + 30 + (index * width) / 5)
          return Math.max(pre, maxWidth)
        }, 0)

        const { height: lineHeight } = {
          ...DEFAULTLINKCONFIG,
          ...props.linkConfig,
        }
        if (!orgTreeContainer.value) return
        const { offsetWidth, offsetHeight } = orgTreeContainer.value
        const treeHeight = treeDepth * lineHeight + height
        //使用占位最长的层级
        const curHeight = Math.max(treeHeight, offsetHeight)
        const curWidth = Math.max(offsetWidth, maxNodesWidth)
        treeMap = d3
          .tree()
          .size([curWidth - 50, curHeight])
          .separation(() => {
            return 1.1
          })

        svg = d3
          .select(`#${props.treeId}`)
          .append('svg')
          .attr('width', curWidth)
          .attr('height', curHeight + height)
          .append('g')
          .attr('transform', `translate(0,${y})`)
          .attr('width', curWidth)
          .attr('height', curHeight + y)
      }

      /**
       * 创建节点div
       * @param {*} d
       */
      function createNodeDiv(d) {
        const { html, fields } = { ...DEFAULTNODECONFIG, ...props.nodeConfig }
        if (!html) {
          let str = ''
          const dom = fields.reduce((pre, cur, index) => {
            let strHtml = ''
            if (index === 0) {
              strHtml = `<div class="title">${d.data[cur] ?? ''}</div>`
            } else {
              strHtml += `${pre}<div class="des">${d.data[cur] ?? ''}</div>`
            }
            return strHtml
          }, '')
          str = `<div class="org-content-wrap "> ${dom}</div>`
          return str
        }
        const html1 = html.replace(/\{\{(\w+|d+)\}\}/g, (word, $1) => {
          return d.data[$1] ?? ''
        })
        return html1
      }

      /**
       * 创建links
       */
      const createLinks = (nodes) => {
        const { lineColor, textColor, textFields } = {
          ...DEFAULTLINKCONFIG,
          ...props.linkConfig,
        }

        const { width } = { ...DEFAULTNODECONFIG, ...props.nodeConfig }
        const links = nodes.links()
        const link = svg.selectAll('.link').data(links)

        link
          .enter()
          .append('path')
          .attr('class', 'link')
          .attr('fill', 'none')
          .attr('stroke-width', 1)
          .attr('stroke', lineColor)
          .attr(
            'd',
            d3
              .linkVertical() // linkVertical() 垂直  linkHorizontal() 水平
              .x(function (d) {
                return d.x + width / 2 - width / 8
              })
              .y(function (d) {
                return d.y
              })
          )
          .attr('marker-end', 'url(#resolved)')

        //华线条文字
        textFields.forEach((item, index) => {
          link
            .enter()
            .append('g')
            .attr('transform', function (d) {
              const x = d.target.x + 10
              const y = d.target.y - 40 + index * 15
              return `translate(${x},${y})`
            })
            .append('text')
            .attr('dy', '.33em')
            .attr('font-size', '12px')
            .attr('fill', textColor)
            .attr('writing-mode', item.direction || 'lr')
            .text(function (d) {
              const text = item.isSource
                ? d.source.data[item.key]
                : d.target.data[item.key]

              return text
            })
        })
      }

      /**
       * 创建nodes
       * @param {*} nodes
       */
      const createNodes = (nodesDes) => {
        const { width, height } = { ...DEFAULTNODECONFIG, ...props.nodeConfig }
        const node = svg
          .selectAll('.node')
          .data(nodesDes)
          .enter()
          .append('g')
          .attr('class', function (d) {
            return `node${d.children ? ' node--internal' : ' node--leaf'}`
          })
          .attr('transform', function (d) {
            const x = d.x - 25
            const y = d.y
            return `translate(${x},${y})`
          })
          .on('click', ($event, d) => {
            d.depth !== 0 && clickNode(d) //根节点不执行点击事件
          })

        node
          .append('foreignObject')
          .attr('class', (d) => {
            const className = d.depth === 0 ? 'first-org-oreign' : ''
            return `org-oreign ${className}`
          })
          .attr('width', width)
          .attr('height', height)
          .append('xhtml:div')
          .html((d) => {
            return createNodeDiv(d)
          })

        drawCircle(node)
      }

      /**
       * 创建树以及节点
       */
      function updateTree() {
        const { height } = {
          ...DEFAULTLINKCONFIG,
          ...props.linkConfig,
        }
        initSvgTree()
        //箭头
        createMark(svg)

        const dataSet = d3.hierarchy(treeData)
        const nodes = treeMap(dataSet)
        const nodeInfos = nodes.descendants()
        nodeInfos.forEach((d) => {
          d.y = height * d.depth
        })

        //获取link以及创建link
        createLinks(nodes)

        //获取node以及创建node
        createNodes(nodeInfos)
      }

      onUnmounted(() => {
        clearTimeout(timeContaniner)
        cleanup()
      })

      const cleanup = useEventListener('resize', () => {
        d3.select(`#${props.treeId}`).selectAll('svg').remove()
        updateTree()
      })

      watch(
        () => props.data,
        (newVal) => {
          if (!props.url) {
            treeData = newVal
            nextTick(() => {
              timeContaniner = setTimeout(() => {
                d3.select(`#${props.treeId}`).selectAll('svg').remove()
                updateTree()
              }, 350)
            })
          }
        },
        {
          deep: true,
          immediate: true,
        }
      )

      return {
        orgTreeContainer,
      }
    },
  }
</script>
<style>
  .org-tree-wrap {
    flex: 1;
    width: 100%;
  }
  .foreign_add {
    width: 10px;
    height: 10px;
    border-radius: 5px;
  }

  .org-oreign {
    padding: 0 10px;
    font-size: 12px;
    background-color: #4ae;
    border-radius: 5px;
  }

  .first-org-oreign {
    position: relative;
    transform: translateY(-40px);
  }

  .org-oreign div {
    line-height: 25px;
  }

  .org-oreign .title {
    font-weight: bold;
  }
</style>
